﻿using UnityEngine;
using System.Collections.Generic;
using ILRuntime.Other;
using System;
using System.Reflection;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.TypeSystem;
using ILRuntime.Runtime.Stack;

namespace Trinity
{
    public unsafe class QuaternionBinder : ValueTypeBinder<Quaternion>
    {
        Vector3Binder vector3Binder;
        bool vector3BinderGot;

        Vector3Binder Vector3Binder
        {
            get
            {
                if (!vector3BinderGot)
                {
                    vector3BinderGot = true;
                    var vector3Type = CLRType.AppDomain.GetType(typeof(Vector3)) as CLRType;
                    vector3Binder = vector3Type.ValueTypeBinder as Vector3Binder;
                }

                return vector3Binder;
            }
        }

        public override unsafe void AssignFromStack(ref Quaternion ins, StackObject* ptr, IList<object> mStack)
        {
            var v = ILIntepreter.Minus(ptr, 1);
            ins.w = *(float*)&v->Value;
            v = ILIntepreter.Minus(ptr, 2);
            ins.x = *(float*)&v->Value;
            v = ILIntepreter.Minus(ptr, 3);
            ins.y = *(float*)&v->Value;
            v = ILIntepreter.Minus(ptr, 4);
            ins.z = *(float*)&v->Value;
        }

        public override unsafe void CopyValueTypeToStack(ref Quaternion ins, StackObject* ptr, IList<object> mStack)
        {
            var v = ILIntepreter.Minus(ptr, 1);
            *(float*)&v->Value = ins.w;
            v = ILIntepreter.Minus(ptr, 2);
            *(float*)&v->Value = ins.x;
            v = ILIntepreter.Minus(ptr, 3);
            *(float*)&v->Value = ins.y;
            v = ILIntepreter.Minus(ptr, 4);
            *(float*)&v->Value = ins.z;
        }
        public override void RegisterCLRRedirection(ILRuntime.Runtime.Enviorment.AppDomain appdomain)
        {
            BindingFlags flag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
            MethodBase method;
            Type[] args;
            Type type = typeof(Quaternion);
            args = new Type[] { typeof(float), typeof(float), typeof(float), typeof(float) };
            method = type.GetConstructor(flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, NewQuaternion);

            args = new Type[] { typeof(Quaternion), typeof(Quaternion) };
            method = type.GetMethod("op_Multiply", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Multiply);

            args = new Type[] { typeof(Quaternion), typeof(Vector3) };
            method = type.GetMethod("op_Multiply", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Multiply2);

            args = new Type[] { typeof(Quaternion), typeof(Quaternion) };
            method = type.GetMethod("op_Equality", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Equality);

            args = new Type[] { typeof(Quaternion), typeof(Quaternion) };
            method = type.GetMethod("op_Inequality", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Inequality);

            args = new Type[] { typeof(Quaternion), typeof(Quaternion) };
            method = type.GetMethod("Dot", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Dot);

            args = new Type[] { typeof(Quaternion), typeof(Quaternion) };
            method = type.GetMethod("Angle", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Angle);

            args = new Type[] { typeof(Vector3) };
            method = type.GetMethod("Euler", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Euler);

            args = new Type[] { typeof(float), typeof(float), typeof(float) };
            method = type.GetMethod("Euler", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Quaternion_Euler2);

            args = new Type[] { };
            method = type.GetMethod("get_eulerAngles", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Get_EulerAngle);

            args = new Type[] { };
            method = type.GetMethod("get_identity", flag, null, args, null);
            appdomain.RegisterCLRMethodRedirection(method, Get_Identity);
        }

        StackObject* Quaternion_Multiply(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 2);
            Quaternion left, right;

            var ptr = ILIntepreter.Minus(esp, 1);
            ParseQuaternion(out right, intp, ptr, mStack);

            ptr = ILIntepreter.Minus(esp, 2);
            ParseQuaternion(out left, intp, ptr, mStack);

            var res = left * right;
            PushQuaternion(ref res, intp, ret, mStack);

            return ret + 1;
        }

        StackObject* Quaternion_Multiply2(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 2);
            Vector3 vec;
            Quaternion left;

            var ptr = ILIntepreter.Minus(esp, 1);
            Vector3Binder.ParseVector3(out vec, intp, ptr, mStack);

            ptr = ILIntepreter.Minus(esp, 2);
            ParseQuaternion(out left, intp, ptr, mStack);

            vec = left * vec;
            PushVector3(ref vec, intp, ret, mStack);

            return ret + 1;
        }

        StackObject* Quaternion_Equality(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 2);
            var ptr = ILIntepreter.Minus(esp, 1);

            Quaternion left, right;
            ParseQuaternion(out right, intp, ptr, mStack);

            ptr = ILIntepreter.Minus(esp, 2);
            ParseQuaternion(out left, intp, ptr, mStack);

            var res = left == right;

            ret->ObjectType = ObjectTypes.Integer;
            ret->Value = res ? 1 : 0;
            return ret + 1;
        }

        StackObject* Quaternion_Inequality(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 2);
            var ptr = ILIntepreter.Minus(esp, 1);

            Quaternion left, right;
            ParseQuaternion(out right, intp, ptr, mStack);

            ptr = ILIntepreter.Minus(esp, 2);
            ParseQuaternion(out left, intp, ptr, mStack);

            var res = left != right;

            ret->ObjectType = ObjectTypes.Integer;
            ret->Value = res ? 1 : 0;
            return ret + 1;
        }

        StackObject* Quaternion_Dot(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 2);
            var ptr = ILIntepreter.Minus(esp, 1);

            Quaternion left, right;
            ParseQuaternion(out right, intp, ptr, mStack);

            ptr = ILIntepreter.Minus(esp, 2);
            ParseQuaternion(out left, intp, ptr, mStack);

            var res = Quaternion.Dot(left, right);

            ret->ObjectType = ObjectTypes.Float;
            *(float*)&ret->Value = res;
            return ret + 1;
        }

        StackObject* Quaternion_Angle(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 2);
            var ptr = ILIntepreter.Minus(esp, 1);

            Quaternion left, right;
            ParseQuaternion(out right, intp, ptr, mStack);

            ptr = ILIntepreter.Minus(esp, 2);
            ParseQuaternion(out left, intp, ptr, mStack);

            var res = Quaternion.Angle(left, right);

            ret->ObjectType = ObjectTypes.Float;
            *(float*)&ret->Value = res;
            return ret + 1;
        }

        StackObject* Quaternion_Euler(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 1);
            var ptr = ILIntepreter.Minus(esp, 1);

            Vector3 vec;
            Vector3Binder.ParseVector3(out vec, intp, ptr, mStack);

            var res = Quaternion.Euler(vec);

            PushQuaternion(ref res, intp, ptr, mStack);
            return ret + 1;
        }

        StackObject* Quaternion_Euler2(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 3);
            float x, y, z;

            var ptr = ILIntepreter.GetObjectAndResolveReference(ILIntepreter.Minus(esp, 1));
            z = *(float*)&ptr->Value;

            ptr = ILIntepreter.GetObjectAndResolveReference(ILIntepreter.Minus(esp, 2));
            y = *(float*)&ptr->Value;

            ptr = ILIntepreter.GetObjectAndResolveReference(ILIntepreter.Minus(esp, 3));
            x = *(float*)&ptr->Value;

            var res = Quaternion.Euler(x, y, z);

            PushQuaternion(ref res, intp, ptr, mStack);
            return ret + 1;
        }

        StackObject* NewQuaternion(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            StackObject* ret;
            if (isNewObj)
            {
                ret = ILIntepreter.Minus(esp, 3);
                Quaternion vec;
                var ptr = ILIntepreter.Minus(esp, 1);
                vec.w = *(float*)&ptr->Value;
                ptr = ILIntepreter.Minus(esp, 2);
                vec.z = *(float*)&ptr->Value;
                ptr = ILIntepreter.Minus(esp, 3);
                vec.y = *(float*)&ptr->Value;
                ptr = ILIntepreter.Minus(esp, 4);
                vec.x = *(float*)&ptr->Value;

                PushQuaternion(ref vec, intp, ptr, mStack);
            }
            else
            {
                ret = ILIntepreter.Minus(esp, 5);
                var instance = ILIntepreter.GetObjectAndResolveReference(ret);
                var dst = *(StackObject**)&instance->Value;
                var f = ILIntepreter.Minus(dst, 2);
                var v = ILIntepreter.Minus(esp, 4);
                *f = *v;

                f = ILIntepreter.Minus(dst, 3);
                v = ILIntepreter.Minus(esp, 3);
                *f = *v;

                f = ILIntepreter.Minus(dst, 4);
                v = ILIntepreter.Minus(esp, 2);
                *f = *v;

                f = ILIntepreter.Minus(dst, 1);
                v = ILIntepreter.Minus(esp, 1);
                *f = *v;
            }
            return ret;
        }

        StackObject* Get_EulerAngle(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = ILIntepreter.Minus(esp, 1);

            var ptr = ILIntepreter.Minus(esp, 1);
            Quaternion vec;
            ParseQuaternion(out vec, intp, ptr, mStack);

            var res = vec.eulerAngles;

            PushVector3(ref res, intp, ret, mStack);
            return ret + 1;
        }

        StackObject* Get_Identity(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
        {
            var ret = esp;

            var res = Quaternion.identity;
            PushQuaternion(ref res, intp, ret, mStack);
            return ret + 1;
        }

        static void ParseQuaternion(out Quaternion vec, ILIntepreter intp, StackObject* ptr, IList<object> mStack)
        {
            var a = ILIntepreter.GetObjectAndResolveReference(ptr);
            if (a->ObjectType == ObjectTypes.ValueTypeObjectReference)
            {
                var src = *(StackObject**)&a->Value;
                vec.w = *(float*)&ILIntepreter.Minus(src, 1)->Value;
                vec.x = *(float*)&ILIntepreter.Minus(src, 2)->Value;
                vec.y = *(float*)&ILIntepreter.Minus(src, 3)->Value;
                vec.z = *(float*)&ILIntepreter.Minus(src, 4)->Value;
                intp.FreeStackValueType(ptr);
            }
            else
            {
                vec = (Quaternion)StackObject.ToObject(a, intp.AppDomain, mStack);
                intp.Free(ptr);
            }
        }

        void PushQuaternion(ref Quaternion vec, ILIntepreter intp, StackObject* ptr, IList<object> mStack)
        {
            intp.AllocValueType(ptr, CLRType);
            var dst = *((StackObject**)&ptr->Value);
            CopyValueTypeToStack(ref vec, dst, mStack);
        }

        void PushVector3(ref Vector3 vec, ILIntepreter intp, StackObject* ptr, IList<object> mStack)
        {
            var binder = Vector3Binder;
            if (binder != null)
                binder.PushVector3(ref vec, intp, ptr, mStack);
            else
                ILIntepreter.PushObject(ptr, mStack, vec, true);
        }
    }
}


